/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using IndexOutput = Lucene.Net.Store.IndexOutput;
using RAMOutputStream = Lucene.Net.Store.RAMOutputStream;
using ArrayUtil = Lucene.Net.Util.ArrayUtil;

namespace Lucene.Net.Index
{
    sealed class TermVectorsTermsWriter:TermsHashConsumer
    {
        private void  InitBlock()
        {
            docFreeList = new PerDoc[1];
        }
        
        internal DocumentsWriter docWriter;
        internal TermVectorsWriter termVectorsWriter;
        internal PerDoc[] docFreeList;
        internal int freeCount;
        internal IndexOutput tvx;
        internal IndexOutput tvd;
        internal IndexOutput tvf;
        internal int lastDocID;
        
        public TermVectorsTermsWriter(DocumentsWriter docWriter)
        {
            InitBlock();
            this.docWriter = docWriter;
        }
        
        public override TermsHashConsumerPerThread AddThread(TermsHashPerThread termsHashPerThread)
        {
            return new TermVectorsTermsWriterPerThread(termsHashPerThread, this);
        }
        
        internal override void  CreatePostings(RawPostingList[] postings, int start, int count)
        {
            int end = start + count;
            for (int i = start; i < end; i++)
                postings[i] = new PostingList();
        }

        public override void Flush(IDictionary<TermsHashConsumerPerThread, ICollection<TermsHashConsumerPerField>> threadsAndFields, SegmentWriteState state)
        {
            lock (this)
            {
                // NOTE: it's possible that all documents seen in this segment
                // hit non-aborting exceptions, in which case we will
                // not have yet init'd the TermVectorsWriter.  This is
                // actually OK (unlike in the stored fields case)
                // because, although IieldInfos.hasVectors() will return
                // true, the TermVectorsReader gracefully handles
                // non-existence of the term vectors files.
                if (tvx != null)
                {
                    
                    if (state.numDocsInStore > 0)
                    // In case there are some final documents that we
                    // didn't see (because they hit a non-aborting exception):
                        Fill(state.numDocsInStore - docWriter.DocStoreOffset);
                    
                    tvx.Flush();
                    tvd.Flush();
                    tvf.Flush();
                }

                foreach(var entry in threadsAndFields)
                {
                    foreach(var field in entry.Value)
                    {
                        TermVectorsTermsWriterPerField perField = (TermVectorsTermsWriterPerField)field;
                        perField.termsHashPerField.Reset();
                        perField.ShrinkHash();
                    }
                    
                    TermVectorsTermsWriterPerThread perThread = (TermVectorsTermsWriterPerThread) entry.Key;
                    perThread.termsHashPerThread.Reset(true);
                }
            }
        }
        
        internal override void  CloseDocStore(SegmentWriteState state)
        {
            lock (this)
            {
                if (tvx != null)
                {
                    // At least one doc in this run had term vectors
                    // enabled
                    Fill(state.numDocsInStore - docWriter.DocStoreOffset);
                    tvx.Close();
                    tvf.Close();
                    tvd.Close();
                    tvx = null;
                    System.Diagnostics.Debug.Assert(state.docStoreSegmentName != null);
                    System.String fileName = state.docStoreSegmentName + "." + IndexFileNames.VECTORS_INDEX_EXTENSION;
                    if (4 + ((long) state.numDocsInStore) * 16 != state.directory.FileLength(fileName))
                        throw new System.SystemException("after flush: tvx size mismatch: " + state.numDocsInStore + " docs vs " + state.directory.FileLength(fileName) + " length in bytes of " + fileName + " file exists?=" + state.directory.FileExists(fileName));
                    
                    state.flushedFiles.Add(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_INDEX_EXTENSION);
                    state.flushedFiles.Add(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION);
                    state.flushedFiles.Add(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION);
                    
                    docWriter.RemoveOpenFile(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_INDEX_EXTENSION);
                    docWriter.RemoveOpenFile(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION);
                    docWriter.RemoveOpenFile(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION);
                    
                    lastDocID = 0;
                }
            }
        }
        
        internal int allocCount;
        
        internal PerDoc GetPerDoc()
        {
            lock (this)
            {
                if (freeCount == 0)
                {
                    allocCount++;
                    if (allocCount > docFreeList.Length)
                    {
                        // Grow our free list up front to make sure we have
                        // enough space to recycle all outstanding PerDoc
                        // instances
                        System.Diagnostics.Debug.Assert(allocCount == 1 + docFreeList.Length);
                        docFreeList = new PerDoc[ArrayUtil.GetNextSize(allocCount)];
                    }
                    return new PerDoc(this);
                }
                else
                    return docFreeList[--freeCount];
            }
        }
        
        /// <summary>Fills in no-term-vectors for all docs we haven't seen
        /// since the last doc that had term vectors. 
        /// </summary>
        internal void  Fill(int docID)
        {
            int docStoreOffset = docWriter.DocStoreOffset;
            int end = docID + docStoreOffset;
            if (lastDocID < end)
            {
                long tvfPosition = tvf.FilePointer;
                while (lastDocID < end)
                {
                    tvx.WriteLong(tvd.FilePointer);
                    tvd.WriteVInt(0);
                    tvx.WriteLong(tvfPosition);
                    lastDocID++;
                }
            }
        }
        
        internal void  InitTermVectorsWriter()
        {
            lock (this)
            {
                if (tvx == null)
                {
                    
                    System.String docStoreSegment = docWriter.DocStoreSegment;
                    
                    if (docStoreSegment == null)
                        return ;
                    
                    System.Diagnostics.Debug.Assert(docStoreSegment != null);
                    
                    // If we hit an exception while init'ing the term
                    // vector output files, we must abort this segment
                    // because those files will be in an unknown
                    // state:
                    tvx = docWriter.directory.CreateOutput(docStoreSegment + "." + IndexFileNames.VECTORS_INDEX_EXTENSION);
                    tvd = docWriter.directory.CreateOutput(docStoreSegment + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION);
                    tvf = docWriter.directory.CreateOutput(docStoreSegment + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION);
                    
                    tvx.WriteInt(TermVectorsReader.FORMAT_CURRENT);
                    tvd.WriteInt(TermVectorsReader.FORMAT_CURRENT);
                    tvf.WriteInt(TermVectorsReader.FORMAT_CURRENT);
                    
                    docWriter.AddOpenFile(docStoreSegment + "." + IndexFileNames.VECTORS_INDEX_EXTENSION);
                    docWriter.AddOpenFile(docStoreSegment + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION);
                    docWriter.AddOpenFile(docStoreSegment + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION);
                    
                    lastDocID = 0;
                }
            }
        }
        
        internal void  FinishDocument(PerDoc perDoc)
        {
            lock (this)
            {
                
                System.Diagnostics.Debug.Assert(docWriter.writer.TestPoint("TermVectorsTermsWriter.finishDocument start"));
                
                InitTermVectorsWriter();
                
                Fill(perDoc.docID);
                
                // Append term vectors to the real outputs:
                tvx.WriteLong(tvd.FilePointer);
                tvx.WriteLong(tvf.FilePointer);
                tvd.WriteVInt(perDoc.numVectorFields);
                if (perDoc.numVectorFields > 0)
                {
                    for (int i = 0; i < perDoc.numVectorFields; i++)
                        tvd.WriteVInt(perDoc.fieldNumbers[i]);
                    System.Diagnostics.Debug.Assert(0 == perDoc.fieldPointers [0]);
                    long lastPos = perDoc.fieldPointers[0];
                    for (int i = 1; i < perDoc.numVectorFields; i++)
                    {
                        long pos = perDoc.fieldPointers[i];
                        tvd.WriteVLong(pos - lastPos);
                        lastPos = pos;
                    }
                    perDoc.perDocTvf.WriteTo(tvf);
                    perDoc.numVectorFields = 0;
                }
                
                System.Diagnostics.Debug.Assert(lastDocID == perDoc.docID + docWriter.DocStoreOffset);
                
                lastDocID++;
                perDoc.Reset();
                Free(perDoc);
                System.Diagnostics.Debug.Assert(docWriter.writer.TestPoint("TermVectorsTermsWriter.finishDocument end"));
            }
        }
        
        public bool FreeRAM()
        {
            // We don't hold any state beyond one doc, so we don't
            // free persistent RAM here
            return false;
        }
        
        public override void  Abort()
        {
            if (tvx != null)
            {
                try
                {
                    tvx.Close();
                }
                catch (System.Exception)
                {
                }
                tvx = null;
            }
            if (tvd != null)
            {
                try
                {
                    tvd.Close();
                }
                catch (System.Exception)
                {
                }
                tvd = null;
            }
            if (tvf != null)
            {
                try
                {
                    tvf.Close();
                }
                catch (System.Exception)
                {
                }
                tvf = null;
            }
            lastDocID = 0;
        }
        
        internal void  Free(PerDoc doc)
        {
            lock (this)
            {
                System.Diagnostics.Debug.Assert(freeCount < docFreeList.Length);
                docFreeList[freeCount++] = doc;
            }
        }
        
        internal class PerDoc:DocumentsWriter.DocWriter
        {
            public PerDoc(TermVectorsTermsWriter enclosingInstance)
            {
                InitBlock(enclosingInstance);
            }
            private void  InitBlock(TermVectorsTermsWriter enclosingInstance)
            {
                this.enclosingInstance = enclosingInstance;
                buffer = enclosingInstance.docWriter.NewPerDocBuffer();
                perDocTvf = new RAMOutputStream(buffer);
            }
            private TermVectorsTermsWriter enclosingInstance;
            public TermVectorsTermsWriter Enclosing_Instance
            {
                get
                {
                    return enclosingInstance;
                }
                
            }
            
            internal DocumentsWriter.PerDocBuffer buffer;
            internal RAMOutputStream perDocTvf;
            internal int numVectorFields;
            
            internal int[] fieldNumbers = new int[1];
            internal long[] fieldPointers = new long[1];
            
            internal void  Reset()
            {
                perDocTvf.Reset();
                buffer.Recycle();
                numVectorFields = 0;
            }
            
            public override void  Abort()
            {
                Reset();
                Enclosing_Instance.Free(this);
            }
            
            internal void  AddField(int fieldNumber)
            {
                if (numVectorFields == fieldNumbers.Length)
                {
                    fieldNumbers = ArrayUtil.Grow(fieldNumbers);
                    fieldPointers = ArrayUtil.Grow(fieldPointers);
                }
                fieldNumbers[numVectorFields] = fieldNumber;
                fieldPointers[numVectorFields] = perDocTvf.FilePointer;
                numVectorFields++;
            }
            
            public override long SizeInBytes()
            {
                return buffer.SizeInBytes;
            }
            
            public override void  Finish()
            {
                Enclosing_Instance.FinishDocument(this);
            }
        }
        
        internal sealed class PostingList:RawPostingList
        {
            internal int freq; // How many times this term occurred in the current doc
            internal int lastOffset; // Last offset we saw
            internal int lastPosition; // Last position where this term occurred
        }
        
        internal override int BytesPerPosting()
        {
            return RawPostingList.BYTES_SIZE + 3 * DocumentsWriter.INT_NUM_BYTE;
        }
    }
}